package com.xyoto.community.service.impl;

import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xyoto.community.cache.CacheInstruction;
import com.xyoto.community.constant.DatabaseOperationResult;
import com.xyoto.community.constant.WechatConstant;
import com.xyoto.community.dto.WxLoginDTO;
import com.xyoto.community.cache.UserCache;
import com.xyoto.community.entity.main.User;
import com.xyoto.community.entity.wechat.AuthMessage;
import com.xyoto.community.entity.wechat.WxInfo;
import com.xyoto.community.entity.wechat.WxUserMessage;
import com.xyoto.community.enums.UserEnum;
import com.xyoto.community.enums.WechatLoginEnum;
import com.xyoto.community.exception.ExceptionUtils;
import com.xyoto.community.mapper.main.UserMapper;
import com.xyoto.community.service.WechatAuthService;
import com.xyoto.community.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.concurrent.TimeUnit;

/**
 * @author coderFu
 * @create 2021/10/2
 */
@Slf4j
@Service
public class WechatAuthServiceImpl implements WechatAuthService {

    @Resource
    private HttpServletRequest request;


    @Resource
    private UserMapper userMapper;

    @Autowired
    private RestTemplate restTemplate;

    @Resource
    private RedisUtils redisUtils;

    @Resource
    private RedisCommonUtils redisCommonUtils;





    @Override
    public Result wechatLogin(WxLoginDTO wxLoginDTO) {
        WxInfo wxInfo = getSession(wxLoginDTO.getCode());
        //获取用户的手机号
        String phone = getUserPhone(wxLoginDTO, wxInfo.getSession_key());
        //判断用户是否存在,并查询信息
        Integer integer = userMapper.judgeUserExistStatus(wxInfo.getOpenid());
        User user = new User();
        if (integer < DatabaseOperationResult.SUCCESS) {
            //用户不存在，需要保存用户数据,无需考虑更新的问题
            user.setAuthenticationStatus((short) 1);
            user.setDisableStatus((short) 1);
            user.setVip((short) 1);
            user.setPhone(phone);
            user.setOpenid(wxInfo.getOpenid());
            //转换性别
            user.setGender(SexTransformUtil.transformSex(wxLoginDTO.getGender()));
            user.setAvatarUrl(wxLoginDTO.getAvatarUrl());
            user.setHot(0);
            user.setNickname(wxLoginDTO.getNickname());
            user.setUnionid(wxInfo.getUnionid());
            user.setCountry(wxLoginDTO.getCountry());
            user.setProvince(wxLoginDTO.getProvince());
            user.setCity(wxLoginDTO.getCity());
            user.setSignature("别看我只是一只羊～");
            log.info("开始存储用户信息");
            Integer integer1 = userMapper.saveUser(user);
            if (integer1 < DatabaseOperationResult.SUCCESS) {
                ExceptionUtils.error(UserEnum.USER_CREATE_FAIL);
            }
        }
        log.info("用户创建成功");
        //查询用户id
        UserCache userCache = userMapper.getUserMessageByOpenid(wxInfo.getOpenid());
        //生成Token令牌
        String token = JwtUtils.getJwtToken(userCache.getId(), userCache.getNickname());
        //将用户信息存储到redis中
        redisCommonUtils.set(token, userCache.getId(),30L, TimeUnit.DAYS);
        //将用户信息添加到用户缓存组中
        redisCommonUtils.hset(CacheInstruction.USER_CACHE,userCache.getId(),userCache);
        //返回给前端信息
        AuthMessage authMessage = new AuthMessage();
        authMessage.setToken(token);
        authMessage.setUserId(userCache.getId());
        return Result.success(authMessage);
    }

    @Override
    public Result checkToken() {
        //检查Token的有效性
        boolean flag = redisUtils.checkTokenStatus(request);
        if (flag){
            try {
                UserCache userCache = redisUtils.getRedisUserMessage(request);
                String token = redisUtils.getUserToken(request);
                //Token有效，更新token的日期
                log.info("更新redis中token的有效时间");
                redisCommonUtils.set(token, userCache.getId(),30L,TimeUnit.DAYS);
                //将用户信息添加到用户缓存组中
                redisCommonUtils.hset(CacheInstruction.USER_CACHE,userCache.getId(),userCache);
                log.info("更新redis中token的有效时间成功");
            }catch (Exception e){
                log.info("大概率是【序列化的对象无法匹配】，很小可能是redis挂掉");
                ExceptionUtils.error(WechatLoginEnum.INVALID_TOKEN);
            }
            return Result.success(WechatLoginEnum.TOKEN_EXPIRE_TIME_ALREADY_UPDATE);
        }
        ExceptionUtils.error(WechatLoginEnum.INVALID_TOKEN);
        return Result.success();
    }

    @Override
    public Result devAuthLogin() {
        //判断之前是否已有模拟用户
        Integer integer = userMapper.judgeUserExistStatus("String");
        UserCache userCache = new UserCache();
        if (integer < DatabaseOperationResult.SUCCESS){
            User user = new User();
            user.setAuthenticationStatus((short)1);
            user.setDisableStatus((short)1);
            user.setVip((short)1);
            user.setHot(0);
            //不存在模拟用户 ，模拟一个微信用户
            WxUserMessage wxUserMessage = new WxUserMessage();
            wxUserMessage.setOpenid("String");
            wxUserMessage.setPhone("12345678901");
            wxUserMessage.setAvatarUrl("String");
            wxUserMessage.setNickname("developer");
            BeanUtils.copyProperties(wxUserMessage,user);
            BeanUtils.copyProperties(wxUserMessage, userCache);
            //存储用户信息到数据库
            Integer integer1 = userMapper.saveUser(user);
            if (integer1<DatabaseOperationResult.SUCCESS){
                log.info("模拟用户数据插入失败");
            }
            log.info("模拟用户插入成功");
            //将用户id存储到userMessage中
            userCache.setId(user.getId());
        }else {
            //已存在模拟用户 ,查询用户信息
            log.info("查询开发者用户信息");
            userCache = userMapper.getUserMessageByOpenid("String");
        }
        //生成Token令牌
        String token = JwtUtils.getJwtToken(userCache.getId(), userCache.getNickname());
        //将用户信息存储到redis中
        redisCommonUtils.set(token, userCache.getId(),30L, TimeUnit.DAYS);
        //将用户信息添加到用户缓存组中
        redisCommonUtils.hset(CacheInstruction.USER_CACHE,userCache.getId(),userCache);
        AuthMessage authMessage = new AuthMessage();
        authMessage.setToken(token);
        authMessage.setUserId(userCache.getId());
        return Result.success(authMessage);
    }

    /**
     * 调用微信接口获取session_key, openid ，进行登录凭证校验
     *
     * @param code
     * @return
     */
    public WxInfo getSession(String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?" +
                "appid=" + WechatConstant.APP_ID + "&secret=" + WechatConstant.SECRET + "&js_code=" + code + "&grant_type=authorization_code";
        ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
        String body = forEntity.getBody();
        if (body == null) {
            //请求接口出现错误
            log.info("请求微信认证信息接口错误,很可能是url路径错误");
        }
        ObjectMapper objectMapper = new ObjectMapper();
        WxInfo wxInfo = null;
        try {
            //将body中的数据（json格式）转为java实体
            wxInfo = objectMapper.readValue(body, WxInfo.class);
            //如果没有请求成功走这里，请求成功不走
            if (wxInfo.getOpenid() == null) {
                //code过期的情况
                if (wxInfo.getErrcode() == WechatConstant.EXPIRE_RESULT_STATUS) {
                    ExceptionUtils.error(WechatLoginEnum.CODE_ALREADY_EXPIRE);
                }
                //code重复使用的情况
            }
        } catch (JsonProcessingException e) {
            log.info("body数据转换java实体错误");
        }
        return wxInfo;
    }


    /**
     * 获取用户的手机号
     *
     * @param wxLogin    微信登录参数
     * @param sessionKey 用户密钥
     * @return
     */
    public String getUserPhone(WxLoginDTO wxLogin, String sessionKey) {
        return decryptData(wxLogin.getEncryptedData(), sessionKey, wxLogin.getIv());
    }

    /**
     * 获取加密的手机号信息
     * @param encryptedData 加密的手机号信息
     * @param sessionKey  用户密钥
     * @param iv  加密密钥
     * @return 手机号
     */
    public static String decryptData(String encryptedData, String sessionKey, String iv) {
        byte[] encData = Base64.decode(encryptedData);
        byte[] ivs = Base64.decode(iv);
        byte[] key = Base64.decode(sessionKey);
        String phone = null;
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivs);
        //调用AES/DES加密算法包最精要的就是下面两句话对称加密
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            phone = JSON.parseObject(new String(cipher.doFinal(encData), StandardCharsets.UTF_8)).getString("purePhoneNumber");
        } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) {
            log.info("获取加密手机号信息失败");
            ExceptionUtils.error(WechatLoginEnum.GET_SECRET_FAILED);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            log.info("手机信息类型转换错误");
            ExceptionUtils.error(WechatLoginEnum.PHONE_TYPE_TRANSFORM_FAILED);
        }
        if (phone == null) {
            log.info("获取加密手机号信息失败");
            ExceptionUtils.error(WechatLoginEnum.GET_SECRET_FAILED);
        }
        return phone;
    }

}
